from xlh import MSMS
import sys

# the following are here to read mzXML
import struct
from base64 import b64decode
from xml.dom import minidom

class MSMSAnalysis():
	""" Class to store MS/MS analysis files
	"""
	def __init__(self):
		self._spectraList = []
		self._searchType = ''
	def parseMgfFile(self, mgffile):
		""" Fills class instance with a provided 
		mascot generic format file
		"""
		try:
			fh = open(mgffile, 'r')
		except IOError:
			sys.stderr.write('Missing file ' + mgffile + '\n')
			sys.exit(1)
		n = 0
		self._spectraList = []

#		fileLine = fh.read() #we have the file into a line
#		fh.close()
#		spLines = fileLine.split('END')
#		if '\r\n' in fileLine:
#			eol = '\r\n'
#		elif '\n' in fileLine:
#			eol = '\n'
#		else:
#			eol = '\r'
#		s = fileLine.find('BEGIN')
#		tempList = fileLine[s:].split('END IONS'+eol) # every element is a spectrum as a line
#		tempList = map(lambda y: y.split(eol), tempList) # every element is a spectrum as list
#		tempList = filter(lambda x: 'BEGIN IONS' in x, tempList)	# remove non spectrum element
#		
#		indxs = range(len(tempList))
#		self._spectraList = map(lambda x: MSMS(), indxs)	#initialize MSMS() objects
#		map(lambda x: self._spectraList[x].initWithList(tempList[x]), indxs)
		for line in fh:
			if line[:5] == 'SEARC': continue
			if line[:5] == 'REPTY': continue
			if not line.strip(): continue
			if line[:5] == 'BEGIN':
				temporarySpectrum = MSMS()
				temporaryPeaks = []
			elif line[:7] == 'PEPMASS':
				temporarySpectrum.setPrecursorMz(float(line[8:]))
			elif line[:6] == 'CHARGE':
				temporarySpectrum.setCharge(int(line[7]))
			elif line[:5] == 'TITLE':
				temporarySpectrum.setDescription(line[6:].strip())
			elif line[:3] == 'END':
				temporarySpectrum.setSpectrum(map(lambda x: [float(x[0]), float(x[1])], temporaryPeaks))
				self._spectraList.append(temporarySpectrum)
			else:
				temporaryPeaks.append(line.split())


	def parseMzXML(self, mzxmlfile):
		""" Fills with mzxml file, only msLevel = 2
		"""
		
		mzFile = minidom.parse(mzxmlfile)
		ms2nodes = filter(lambda x: x.attributes["msLevel"].value == '2', mzFile.getElementsByTagName('scan'))
		
		for node in ms2nodes:
			newMSMS = MSMS()
			
			newMSMS.setPrecursorMz(float(node.getElementsByTagName('precursorMz')[0].childNodes[0].nodeValue))
			newMSMS.setCharge(int(node.getElementsByTagName('precursorMz')[0].attributes["precursorCharge"].value))
			tString = 'ScanNumber: ' + str(node.attributes["num"].value) + 'PrecIntensity: ' + str(node.getElementsByTagName('precursorMz')[0].attributes["precursorIntensity"].value)
			newMSMS.setDescription(tString)
			
			rawPeak = node.getElementsByTagName('peaks')[0].childNodes[0].nodeValue

			masses = []
			intensities = []
			decodedString = b64decode(rawPeak)
			decodeMask = '>' + 'f'*(len(decodedString)/4) 
			decodedList = struct.unpack(decodeMask, decodedString)
			

			newMSMS.setSpectrum(map(lambda x: [decodedList[x], decodedList[x+1]], range(0,len(decodedList),2)))
			
			self._spectraList.append(newMSMS)
		
	def dumpMgfFile(self, mgffile):
		""" Dumps class content into a mascot generic format file
		"""
		fh = open(mgffile, 'w')
		fh.write("SEARCH=MIS\nREPTYPE=PEPTIDE\n")
		for s in self._spectraList:
			fh.write("BEGIN IONS\n")
			fh.write("PEPMASS=" + str(s.precursorMz()) + "\n")
			fh.write("CHARGE=" + str(s.charge()) + "+\n")
			fh.write("TITLE=" + s.description() + "\n")
			for p in s.items():
				fh.write(str(p[0]) + " " + str(p[1]) + "\n")
			fh.write("END IONS\n\n")
		fh.close()
#	def fillWithMzxmlFile(self, mzxmlfile):
#		# this will be useful as soon as we understand if the format will be
#		# spread around MS community...
#		fh = open(mzxmlfile, 'r')
#		self._spectraList = []
#		fh.close()
#	def fillWithDtaFile(self, dtafile):
#		fh = open(dtafile, 'r')
#		self._spectraList = []
#		fh.close()
	def searchType(self):
		return self._searchType
	def	spectra(self):
		""" Return the list of stored spectra objects
		"""
		return self._spectraList
	def	items(self):
		""" Return an iterator over spectra
		"""
		return iter(self._spectraList)
	def length(self):
		""" Return the number of parsed spectra 
		"""
		return len(self._spectraList)
	def spectrumAtIndex(self, index = 0):
		""" Return the spectrum at a certain index.
		I think this method is pretty useless
		"""
		try:
			return self._spectraList[index]
		except IndexError:
			return None
	def precursorMzSearch(self, mass, tolerance = 0.5, unit = 'Da'):
		""" Return a new MSMSAnalysis object with all the spectra
		with a precursor m/z equal to the mass provided, within
		a certain tolerace
		"""
		t = MSMSAnalysis()
		mass = float(mass)
		tolerance = float(tolerance)

		if unit == 'Da':
			None
		elif unit == 'ppm':
			tolerance = mass / 1000000 * tolerance
		else:
			return None
			
		t._spectraList = filter(lambda x: abs(x.precursorMz() - mass) <= tolerance, self._spectraList)		
		return t
	def precursorMassSearch(self, mass, tolerance = 0.5, unit = 'Da'):
		""" Return a new MSMSAnalysis object with all the spectra
		with a precursor Mr equal to the mass provided, within
		a certain tolerace
		"""
		t = MSMSAnalysis()
		mass = float(mass)
		tolerance = float(tolerance)

		if unit == 'Da':
			None
		elif unit == 'ppm':
			tolerance = mass / 1000000 * tolerance
		else:
			return None

		t._spectraList = filter(lambda x: abs(x.precursorMass() - mass) <= tolerance, self._spectraList)		
		return t
	def chargeSearch(self, charge):
		""" Return a new MSMSAnalysis object with all the spectra
		with a certain charge value
		"""
		t = MSMSAnalysis()
		charge = int(charge)
		t._spectraList = filter(lambda x: x._charge == charge, self._spectraList)		
		return t
	def descriptionSearch(self, string):
		""" Return a new MSMSAnalysis object with all the spectra
		with given text into spectrum description. Useful for filtering
		selected spectra...
		"""
		t = MSMSAnalysis()
		t._spectraList = filter(lambda x: string in x._description, self._spectraList)
		return t
	def addSpectrum(self, spectrum):
		""" Add a new spectrum to this analysis
		"""
		self._spectraList.append(spectrum)
	def reduceNoise(self, ratio):
		""" Cut every peak that has intensity lower that a
		certain ratio to the max intensity peak
		"""
		map(lambda x: x.reduceNoise(ratio), self.spectra())
